/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.esgi.dns;

import java.nio.ByteBuffer;
import org.esgi.dns.Constant.ClassDNS;
import org.esgi.dns.Constant.Type;
import org.esgi.dns.Message.DomainNameLengthException;
import org.esgi.dns.Message.LabelLengthException;
import org.esgi.dns.RData.InvalidInternetAddress;

/**
 * This class is used to create the Message Resource Record (RR) section.
 * 
 * http://www.ietf.org/rfc/rfc1035.txt
 * (§3.2. ResourceRecord definitions and §4.1.3. Resource record format)
 * 
 * ResourceRecord class describes the basic element of a DNS tree.
 * 
 * The answer, authority, and additional sections all share the same
 * format: a variable number of resource records, where the number of
 * records is specified in the corresponding count field in the header.
 * Each resource record has the following format:
 * 
 *                                   1  1  1  1  1  1
 *     0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
 *   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 *   |                                               |
 *   /                                               /
 *   /                      NAME                     /
 *   |                                               |
 *   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 *   |                      TYPE                     |
 *   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 *   |                     CLASS                     |
 *   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 *   |                      TTL                      |
 *   |                                               |
 *   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 *   |                   RDLENGTH                    |
 *   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
 *   /                     RDATA                     /
 *   /                                               /
 *   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
 * 
 * 
 * @author Alain Janin-Manificat <alain.janinm@hotmail.fr>
 * 
 * @see Message
 */
public class ResourceRecord {

    /**
     * An owner name (domain name), i.e., the name of the node 
     * to which this resource record pertains.
     */
    String NAME;
    
    /**
     * Two octets containing one of the ResourceRecord TYPE codes.
     * This field specifies the meaning of the data in the RDATA field.
     */
    short TYPE;
    
    /**
     * Two octets containing one of the ResourceRecord CLASS codes.
     * Specify the class of the data in the RDATA field.
     */
    short CLASS;
    
    /**
     * A 32 bit signed integer that specifies the time interval
     * that the resource record may be cached before the source
     * of the information should again be consulted.  Zero
     * values are interpreted to mean that the ResourceRecord can only be
     * used for the transaction in progress, and should not be
     * cached.  For example, SOA records are always distributed
     * with a zero TTL to prohibit caching.  Zero values can
     * also be used for extremely volatile data.
     */
    int TTL;
    
    /**
     * An unsigned 16 bit integer that specifies the length in
     * octets of the RDATA field.
     */
    short RDLENGTH;
    
    /**
     * A variable length string of octets that describes the
     * resource.  The format of this information varies
     * according to the TYPE and CLASS of the resource record.
     * For example, the if the TYPE is A and the CLASS is IN,
     * the RDATA field is a 4 octet ARPA Internet address.
     */
    String RDATA;

    
    /**
     * Getter / Setter for ResourceRecord fields.
     */
    public short getCLASS() {
        return CLASS;
    }

    public void setCLASS(short CLASS) {
        this.CLASS = CLASS;
    }

    public String getNAME() {
        return NAME;
    }

    public void setNAME(String NAME) {
        this.NAME = NAME;
    }

    public String getRDATA() {
        return RDATA;
    }

    public void setRDATA(String RDATA) {
        this.RDATA = RDATA;
    }

    public int getRDLENGTHUnsigned() {
        //convert short to unsigned short.
        return (int) (0xFFFF & RDLENGTH);
    }
    public short getRDLENGTH() {
        return RDLENGTH;
    }

    public void setRDLENGTH(int RDLENGTH) {
        //convert unsigned short to short.
        this.RDLENGTH = (short) (0xFFFF & RDLENGTH);
    }

    public long getTTLUnsigned() {
        //convert int to unsigned int.
        return (long) (0xFFFFFFFFL & TTL);
    }
    public int getTTL() {
        return TTL;
    }

    public void setTTL(long TTL) {
        //convert unsigned int to int.
        this.TTL = (int) (0xFFFFFFFFL & TTL);
    }

    public short getTYPE() {
        return TYPE;
    }

    public void setTYPE(short TYPE) {
        this.TYPE = TYPE;
    }

    public ByteBuffer encode(ByteBuffer b) throws LabelLengthException, DomainNameLengthException, InvalidInternetAddress {
        return serialize(this, b);
    }

    /**
     * Serialize the Resource Record (RR) informations in a ByteBuffer,
     * according to the rules defined in :
     * 
     * http://www.ietf.org/rfc/rfc1035.txt
     * (§4.1.3. Resource record format)
     * 
     * @param rr        ResourceRecord
     * @param message   ByteBuffer - The ByteBuffer use to build the Message.
     * @return          ByteBuffer - The reference to the ByteBuffer given in parameter.
     * @throws org.esgi.dns.Message.LabelLengthException
     * @throws org.esgi.dns.Message.DomainNameLengthException 
     */
    public static ByteBuffer serialize(ResourceRecord rr, ByteBuffer message) throws LabelLengthException, DomainNameLengthException, InvalidInternetAddress {

        String name = rr.getNAME();
        /*
         * Check if a pointer to given domain name exists. If true the pointer exists
         * and has been set.
         */
        if (!Message.setPointer(name, message)) {
            Message.setDomainName(name, message);
        }

        message.putShort(rr.getTYPE());
        message.putShort(rr.getCLASS());
        message.putInt(rr.getTTL());
        message.putShort(rr.getRDLENGTH());
        
        /*!!!!!!!!!!!!
         * TODO FINISH RDATA
         * !!!!!!!!!!*/     
        if(rr.getTYPE() == Type.A && rr.getCLASS() == ClassDNS.IN){
            //RDATA field is a 4 octet ARPA Internet address.
            RData.createARData(rr.getRDATA().getBytes(), message);
        }
        else if(rr.getTYPE() == Type.CNAME){
            RData.createCNameRData(rr.getNAME(), message);
        }
        
        return message;
    }

    /**
     * Unserialize a ByteBuffer containing Resource Record informations
     * stored following the format defined in :
     * 
     * http://www.ietf.org/rfc/rfc1035.txt
     * (§4.1.3. Resource record format)
     * 
     * Create an instance of Resource Record from those informations.
     * @param input         ByteBuffer - Contains the header informations.
     * @param RRPosition    int - where the RR start in the ByteBuffer
     * @param RDataLength   int - the length of RDATA
     * @return              ResourceRecord - The ResourceRecord object created from the ByteBuffer informations.
     */
    public static ResourceRecord unserialize(ByteBuffer input, int RRPosition) {
        ResourceRecord rr = new ResourceRecord();
        
        ByteBuffer name = ByteBuffer.allocate(512);
        //Get NAME.
        byte byteName;
        int i = RRPosition;
        while((byteName = input.get(i++))!=0) {
            name.put(byteName);
        }
        int nameEndPos = i-1;
        //Get the size of NAME.
        int nameLength = name.position();
        //Reset the pointer to first byte.
        name.clear();
        String part = "", separator = "";
        
        //Iterate over the ByteBuffer.
        while (name.position() < nameLength) {
            //Read first byte.
            byte b = name.get();
            //Check if it's a pointer
            if ((0xC0 & b) >> 6 == 3) {   //3 -> 11 in binary
                
                //Get the offset of the label in the Message.
                int offset = ((0x3FFF & name.getShort(name.position() - 1)));
                
                //Position the Message buffer at given offset.
                input.position(offset);
                
                //Read the length of the label
                byte len = input.get();
                int length = (0x3F & len);
                
                //Build the label.
                part += separator;
                for (int j = 0; j < length; j++) {
                    part += (char) input.get();
                }
                //Read the second byte (a pointer is 2 bytes); 
                name.get();
            } else {
                //Get the length of the label
                int length = (0x3F & b);
                if (length == 0) {
                    break;
                }
                //Build the label.
                part += separator;
                for (int j = 0; j < length; j++) {
                    part += (char) name.get();
                }
            }
            separator = ".";

        }
        rr.setNAME(part);
        input.position(nameEndPos);
        rr.setTYPE(input.getShort());
        rr.setCLASS(input.getShort());
        rr.setTTL(input.getInt());
        rr.setRDLENGTH(input.getShort());
        ByteBuffer tmpRData = ByteBuffer.allocate(rr.getRDLENGTHUnsigned());
        for(i = input.position() ; i < input.position()+rr.getRDLENGTHUnsigned() ; i++){
            byte b = input.get(i);
            if(b==0)break;
            tmpRData.put(b);
        }
        tmpRData.clear();
        part = "";
        separator = "";
        //Iterate over the ByteBuffer.
        while (tmpRData.position() < i) {
            //Read first byte.
            byte b = tmpRData.get();
            //Check if it's a pointer
            if ((0xC0 & b) >> 6 == 3) {   //3 -> 11 in binary
                
                //Get the offset of the label in the Message.
                int offset = ((0x3FFF & tmpRData.getShort(tmpRData.position() - 1)));
                
                //Position the Message buffer at given offset.
                input.position(offset);
                
                //Read the length of the label
                byte len = input.get();
                int length = (0x3F & len);
                
                //Build the label.
                part += separator;
                for (int j = 0; j < length; j++) {
                    part += (char) input.get();
                }
                //Read the second byte (a pointer is 2 bytes); 
                tmpRData.get();
            } else {
                //Get the length of the label
                int length = (0x3F & b);
                if (length == 0) {
                    break;
                }
                //Build the label.
                part += separator;
                for (int j = 0; j < length; j++) {
                    part += (char) tmpRData.get();
                }
            }
            separator = ".";

        }
        rr.setRDATA(part);
        return rr;
    }
}
